Check out the files for each step for the code up to that point
The tile based movement tutorial will allow the user the gaze at a tile that they would like to move to. The user will be teleported to the center of the tile that they have gazed at. This type of locomotion may be useful when designing scenes where the user is supposed to navigate in a defined space.
This tutorial will require the aframe framework
<!-- A-Frame -->
<script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script>
There will be three assets used in this tutorial. An image that will be used as a glowing effect, another image that will be used as the sky, and an obj file that is a 3D model of a maze.
<a-assets>
<img
id="glow"
crossorigin="anonymous"
src="https://cdn.glitch.com/6371daf2-253b-4584-912d-cdf772194763%2Fgradient.png?v=1594198045978"
/>
<img
id="sky"
crossorigin="anonymous"
src="https://cdn.glitch.com/6371daf2-253b-4584-912d-cdf772194763%2Fgreen-hills-over-town.jpg?v=1594198049857"
/>
<!-- maze model -->
<a-asset-item
id="maze-obj"
crossorigin="anonymous"
src="https://cdn.glitch.com/6371daf2-253b-4584-912d-cdf772194763%2Fmaze.obj?v=1594198049361"
></a-asset-item>
</a-assets>
To create the skybox, simply set the src of the to the id that was given to the sky image in the asset manager
<!-- sky -->
<a-sky src="#sky"></a-sky>
To create the floor container, give an a unique id so that it can be easily referenced. Make sure to set it's rotation to -90 degrees about the X-axis. And give this entity the play-area component and set the size of the play-area to 25. Don't worry about this component just yet as it still has to be written.
<!-- floor -->
<a-entity
id="momentGrid"
position="0 0 0"
rotation="-90 0 0"
play-area="size: 25"
>
</a-entity>
To make the user rig for this tutorial, wrap a cursor inside of a camera inside of an entity, make sure to uniquely id each of the entities of this rig. The outer is required as it is easier to change the position of this entity rather than moving the . If you want to not allow the user to use keyboard WASD controls to move about the scene, you can give the camera the wasd-controls-enabled component and set it equal to false. This is not necessary for this tutorial but it will help demonstrate tile based locomotion. In the it is important to set the fuse and fuse-timeout. This lets the user to look at objects to interact with them, and defines how long it takes in milliseconds it takes for the interaction to take place. The position should be set to 0 0 -0.01, so that the cursor is just in front of the user's eyes. This will ensure that there are no problems later with intersections of different entities. Set the geometry and material of the cursor to whatever shape you would prefer your crosshair to be, but make sure that its size is very small as the cursor is very close the user's eyes. The raycaster component acts as a laser that is used to get information about what the user is looking at, how far things are and much more. Make sure to set the raycaster's object to be .clickable, this will ensure that the raycaster only recognizes objects that are of class clickable. You can set how far the raycaster can travel and you can set the interval in milliseconds between interval tests.
<!-- User Rig for Tile Movement -->
<a-entity id="user">
<a-camera id="camera" wasd-controls-enabled="false">
<a-cursor
id="caster"
fuse="true"
fuse-timeout="1000"
position="0 0 -0.01"
geometry="radiusOuter:0.0003;
radiusInner: 0.0002;
segmentsTheta: 6"
material="color: black;
opacity: 0.5"
raycaster="objects: .clickable; far: 4; interval: 0; showLine: true"
animation__fusing="property: scale; startEvents: fusing; easing: easeInCubic; dur: 1000; from: 1 1 1; to: 0.5 0.5 0.5"
animation__mouseleave="property: scale; startEvents: mouseleave; easing: easeInCubic; dur: 1000; to: 1 1 1"
></a-cursor>
</a-camera>
</a-entity>
This tutorial requires some custom components, there are 3 components that need to be written. Each of the components should be written in a script before the body. To create a component, used AFRAME.registerComponent("component-name", {what this component does}); If you want to read more about writing components have a look at the a-frame documentation https://aframe.io/docs/1.0.0/core/component.html#sidebar
The play-area component is used to create a grid of tiles that the user can use to move about the scene. To allow the user to set the size that they want. Add a schema to this component. In the schema define size to be of type number and set the default value to 10. This means that unless the user explicitly defines what size they want their play-area to be, the default size will be 10x10. Add an init function, this is a function that will run when initialized. To make reference to the parent object, or the container in which the tiles will be added, create a variable parent, and set it to this.el. This means that the element that has play-area component will be the parent. Create a variable size, and set it's value to this.data.size, this will get the user defined size value. Create a variable user, and set it to the user element. The user element can be found by searching the document for the element with the user id. To set the user's location to the center of the play-area, use the a-frame setComponentProperty function to set its position to the approximate center, that can be found by dividing the size by 2. https://aframe.io/docs/1.0.0/core/utils.html#sidebar
To create all of the tiles of the play-area. Use a nested for loop set to terminate when reaching the user defined size. At each point of the loop, we want to create a tile element to place here. To do this, create a tile element, make a tile variable to be . give this tile variable attributes using .setAttribute("attribute", "value"). Set the position of the tile to be dependent on the index values of the nested for loop. Set the class of the tile to be clickable, as the user will be interacting with each tile. Give the tile the teleport and the highlight attribute, these do not have to be given a value and will be defined later on in this tutorial. To create a tile color pattern similar to a chess board, when ever the i and j are even or when the i and j are not even, set the color of the tile to be a light grey, otherwise, set the color of the tile to be a dark grey. Of course you can change these colors to which ever you prefer. At the end of each for loop, append the tile to the parent using parent.appendChild(tile).
// play area component
AFRAME.registerComponent("play-area", {
schema: {
size: { type: "number", default: 10 }
},
init: function() {
var parent = this.el; // reference to the object that has the play-area component
var size = this.data.size; // the size that was specified
var user = document.getElementById("user");
AFRAME.utils.entity.setComponentProperty(user, "position", {
x: size / 2,
z: -(size / 2)
}); // reposition the user to the middle of the play-area
for (var i = 0; i < size; i++) {
for (var j = 0; j < size; j++) {
var tile = document.createElement("a-plane"); // make the tile
// change color of the tiles to make chess board pattern
if ((i % 2 == 0 && j % 2 == 0) | (i % 2 != 0 && j % 2 != 0)) {
tile.setAttribute("color", "#a1a1a1");
} else {
tile.setAttribute("color", "#666666");
}
tile.setAttribute("position", i + " " + j + " 0"); // set position
tile.setAttribute("teleport", ""); // make a teleport tile
tile.setAttribute("class", "clickable"); // make clickable
tile.setAttribute("highlight", ""); // make the tile highlight
parent.appendChild(tile); // add to the scene
}
parent.appendChild(tile); // add to the scene
}
}
});
To improve User Experience, it would be a good idea to highlight the tile that the user is currently looking at. To do this, create a new component called highlight. The highlight component only needs an init function. Add an event listener to this.el .Set the event listener to trigger when there is a raycaster-interescted. This will trigger will the user's raycaster intersects the tile, or when the user is looking at this tile. In this event listener function, create an element called glow. Set the position to 0 0 0.5. Set the rotation to 90 degrees about the X-axis. Using the a-frame setComponentProperty of the glow define the geometry to have a primitive of cone, openEnded to be true, and radiusBottom to be 0.5. Using the setComponentProperty to define the material of glow to have side = front, src= #glow, emission = red and transparent. Note that #glow will be the image in the asset with id = glow. You can set the emission to whatever color you would like the highlight to be. Then append the glow to the tile that the user is looking at with this.appendChild(glow) To remove the highlight when the user to no longer looking at a particular tile, add an event listener that triggers when there is a raycaster-intersected-cleared event. Simply this function set this.innerHTML to be empty. This will remove any children of the tile.
// highlight component
AFRAME.registerComponent("highlight", {
init: function() {
var el = this.el; // reference to this object
// triggers when the user is looking at the tile
this.el.addEventListener("raycaster-intersected", function() {
var glow = document.createElement("a-entity"); // create highlight
glow.setAttribute("position", "0 0 0.5"); // set relative position
glow.setAttribute("rotation", "90 0 0"); // set relative rotation
// define the geometry of the highlight
AFRAME.utils.entity.setComponentProperty(glow, "geometry", {
primitive: "cone",
openEnded: true,
radiusBottom: 0.5
});
// define the material of the highlight
AFRAME.utils.entity.setComponentProperty(glow, "material", {
side: "front",
src: "#glow",
emissive: "red",
transparent: true
});
this.appendChild(glow);
});
// triggers when the user is no longer looking at the tile
this.el.addEventListener("raycaster-intersected-cleared", function() {
this.innerHTML = ""; // clear the highlight
});
}
});
To allow the user to move about the scene, create a new component with the name teleport. The teleport component has an init function that gives this.el an event listener that triggers when ever the user clicks on this element. When the user clicks on this element, and the event is triggered. Create a variable user and set it to the element with id, user. Create a variable pos, this will hold the position information of the user. Set pos equal to the user's current position using the a-frame getComponentProperty function. https://aframe.io/docs/1.0.0/core/utils.html#getcomponentproperty-entity-componentname-delimiter39-39 . Create a variable tile that represents the position of this tile. set tile equal to this.getAttribute("position"). Now that we have the positional information about the user and the tile that they are looking at, set the pos.x = tile.x, pos.y = tile.z, pos.z = -tile.y. Using the tile position, we are changing user position to match the appropriate tile position. Note the pos.y and pos.z are swapped with z and -y tile position to account for the -90 degree X-axis rotation on the floor container. Then using the a-frame setComponentProperty set the user's x y z position to the corresponding pos x y z position
// teleport
AFRAME.registerComponent("teleport", {
init: function() {
// when the user clicks on this
this.el.addEventListener("click", function() {
// make reference to the user
var user = document.getElementById("user");
// get the position of the user
var pos = AFRAME.utils.entity.getComponentProperty(user, "position");
// get the position of this tiled
var tile = this.getAttribute("position");
// set the user position x y z to the appropiate tile x y z
pos.x = tile.x;
pos.y = tile.z;
pos.z = -tile.y;
// set the new user position
AFRAME.utils.entity.setComponentProperty(user, "position", {
x: pos.x,
y: pos.y,
z: pos.z
});
});
}
});
The user is now able to move around by gazing at the tiles
To add the maze object to the scene, create an entity and set the obj-model to the obj file that was added to the asset manager. For the provided maze obj file, set the scale to 0.25 0.25 -0.25 and set the position to -0.75 0 0.8. Give the maze class clickable, this will prevent the user from passing through walls.
<!-- maze -->
<a-entity
id="maze"
obj-model="obj: #maze-obj"
scale="0.25 0.25 -0.25"
position="-0.75 0 0.8"
material="color: red"
class="clickable"
></a-entity>
To position the user at the start of the maze, in the play-area component, instead of setting the position to the center of the set the position to x: 11, y: 0, z: 0. This is the starting position for the provided maze.
// change the position of the user to the start of the maze
AFRAME.utils.entity.setComponentProperty(user, "position", {
//x: size / 2,
//z: -(size / 2)
x: 11,
y: 0,
z: 0
});
Created by: David McNamara